/*
 * Engine Alpha ist eine anfängerorientierte 2D-Gaming Engine.
 *
 * Copyright (c) 2011 - 2014 Michael Andonie and contributors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package ea.internal.phy;

import ea.*;
import ea.internal.util.Logger;

/**
 * Ein Gravitator sorgt fuer das Verhalten eines Aktiv-Raum-Objektes in der Physik.
 *
 * @author Michael Andonie
 */
public class Gravitator extends PhysikClient {

	/**
	 * Die Physik
	 */
	private final Physik physik = Physik.getPhysik();

	/**
	 * Eine imaginaere Pseudo-Beschleunigung fuer die Schwerkraft.
	 */
	private int schwerkraft = 4;

	/**
	 * Der Trend der aktuellen Y-Verschiebung
	 */
	private int yTrend = 0;

	/**
	 * Der Zaehöer des Y-Trends.
	 */
	private int zaehler = 1;

	/**
	 * Gibt an, ob das Objekt in der naechsten TICK-Runde springen soll.
	 */
	private boolean sprungStart = false;

	/**
	 * Ob das Ziel-Objekt im letzten Schritt "gefallen" oder gesprungen ist.
	 */
	private boolean zuletztGefallen = false;

	/**
	 * Ob das Ziel-Raum-Objekt von Schwerkraft beeinflusst wird
	 */
	private boolean hatSchwerkraft = true;

	/**
	 * Das FallReagierbar-Interface, das auf zu tiefes Fallen reagieren soll.
	 */
	private FallReagierbar fListener = FallDummy.getDummy();

	/**
	 * Das StehReagierbar-Interface, das auf stehen reagieren soll.
	 */
	private StehReagierbar sListener = StehDummy.getDummy();

	/**
	 * Die kritische Tiefe, ab der der Listener regelmaessig informiert wird.
	 */
	private int kritischeTiefe;

	private float remainderX=0, remainderY=0;

	/**
	 * Konstruktor.
	 *
	 * @param ziel
	 * 		Ziel des Gravitators: Dieses Objekt wird von ihm überwacht
	 */
	public Gravitator (Raum ziel) {
		super(ziel);
		physik.aktivAnmelden(this);
	}

	/**
	 * Methode zur Weitergabe eines Rechenschrittes
	 *
	 * @param runde
	 * 		Die Runde (1 - 10)
	 */
	public void tick (int runde) {
		ziel.verschieben(physik.entblocken(ziel.dimension()));
		if (ziel.dimension().y > kritischeTiefe) {
			fListener.fallReagieren();
		}
		if (!(runde == 1)) {
			return;
		}
		if (!hatSchwerkraft) {
			return;
		}
		boolean steht = steht();
		if (!steht) {
			//FALL-ZAEHLEN
			if (zaehler % schwerkraft == 0) {
				if (yTrend < 10) {
					zaehler = 1;
					yTrend++;
				}
			} else {
				zaehler++;
			}
		} else if (!sprungStart) {
			if (zuletztGefallen) {
				sListener.stehReagieren();
			}
			yTrend = 0;
			zuletztGefallen = false;
			zaehler = 1;
		} else {
			sprungStart = false;
			zuletztGefallen = false;
		}
		bewegen(new Vektor(0, yTrend));
	}

	/**
	 * Bewegt das Raum-Objekt mithilfe des Gravitators.
	 *
	 * @param v
	 * 		Die Bewegung beschrieben durch einen Vektor.
	 *
	 * @return <code>true</code>, sollte die Bewegung vollfuehrt worden sein, ohne, dass man passiv
	 * geblockt wurde. Wurde man in der vollen Ausfuehrung der Bewegung gehindert, ist die Rueckgabe
	 * <code>false</code>.
	 */
	@Override
	public boolean bewegen (Vektor v) {
		ziel.verschieben(physik.entblocken(ziel.dimension()));
		return (xVersch(v.x) & yVersch(v.y));
	}

	/**
	 * Diese Methode wird immer dann aufgerufen, wenn ein Client nicht weiter benoetigt wird, und er
	 * alle seine Funktionen beenden soll, um die von ihm belegten Ressourcen freizugeben.
	 */
	@Override
	public void aufloesen () {
		physik.aktivAbmelden(this);
	}

	/**
	 * Laesst das anliegende Raum-Objekt springen. Dies ist nur dann möglich, wenn das anliegende
	 * Raum-Objekt "steht". Also, wenn es auf einemm Passiv-Objekt steht.
	 *
	 * @param kraft
	 * 		Die Sprungkraft.
	 */
	@Override
	public boolean sprung (int kraft) {
		if (!hatSchwerkraft) {
			Logger.error("Achtung! Ein Raum-Objekt, fuer das KEINE Schwerkraft gilt, kann nicht springen!");
			return false;
		}
		if (steht()) {
			yTrend = -kraft;
			sprungStart = true;
			return true;
		}
		return false;
	}

	/**
	 * Setzt, ob das Ziel-Objekt von der Schwerkraft beeinflusst wird und somit fallen, aber auch
	 * springen kann.
	 *
	 * @param aktiv
	 * 		Ist dieser Wert <code>true</code>, so wird das Ziel-Objekt von Schwerkraft beeinflusst. Ist
	 * 		er <code>false</code>, dann nicht.
	 */
	@Override
	public void schwerkraftAktivSetzen (boolean aktiv) {
		hatSchwerkraft = aktiv;
	}

	/**
	 * Diese Methode setzt die kritische Tiefe eines Aktiv-Objektes. Ab dieser wird das
	 * entsprechende <code>FallReagierbar</code>-Inteface, <b>das angemeldet wurde</b>, ueber den
	 * Fall informiert.
	 *
	 * @param tiefe
	 * 		Die Tiefe, ab der das anliegende <code>FallReagierbar</code>-Interface informiert werden
	 * 		soll. Als Y-Koordinate.
	 *
	 * @see #fallReagierbarAnmelden(FallReagierbar, int)
	 */
	@Override
	public void kritischeTiefeSetzen (int tiefe) {
		kritischeTiefe = tiefe;
	}

	/**
	 * In dieser Methode wird der <code>FallReagierbar</code>-Listener angemeldet.<br /> Dieser wird
	 * ab sofort <i>immer wieder</i> informiert, solange das Ziel-<code>Raum</code>-Objekt unterhalb
	 * der Toleranzgrenze liegt.
	 *
	 * @param f
	 * 		Das <code>FallReagierbar</code>-Objekt, das ab sofort im Grenzfall informiert wird.
	 * @param tiefe
	 * 		Die kritische Tiefe, ab der das Interface informiert wird.
	 *
	 * @see #kritischeTiefeSetzen(int tiefe)
	 */
	@Override
	public void fallReagierbarAnmelden (FallReagierbar f, int tiefe) {
		fListener = f;
		kritischeTiefe = tiefe;
	}

	/**
	 * In dieser Methode wird der <code>StehReagierbar</code>-Listener angemeldet.<br /> Dieser wird
	 * ab sofort immer dann <i>einmalig</i> informiert, wenn das Ziel-<code>Raum</code>-Objekt nach
	 * einem Sprung/Fall wieder auf einem Passiv-Objekt steht.
	 *
	 * @param s
	 * 		Das <code>StehReagierbar</code>-Objekt, das ab sofort immer einmalig informiert wird, wenn
	 * 		das Ziel-Objekt zum Stehen kommt.
	 */
	public void stehReagierbarAnmelden (StehReagierbar s) {
		sListener = s;
	}

	/**
	 * Testet, ob das hieran anliegende Ziel-Objekt steht.
	 *
	 * @return <code>true</code>, wenn das Ziel-Objekt auf einem Passiv-Objekt steht und somit nicht
	 * faellt oder steigt, sonst <code>false</code>.<br /> <b>Bewegung nach Rechts/Links wird hier
	 * nicht beruecksichtigt.</b>
	 */
	@Override
	public boolean steht () {
		BoundingRechteck test = ziel.dimension().verschobeneInstanz(new Vektor(0, 1));
		return physik.inPassivem(test);
	}

	/**
	 * Setzt die Schwerkraft fuer dieses Objekt.<br /> <b>Achtung:</b> Standardwert: 4<br />
	 * Groesserer Wert = langsamer Fallen<br /> Kleinerer Wert = schneller Fallen <br /> Negativer
	 * Wert : Moege Gott uns allen gnaedig sein...
	 *
	 * @param schwerkraft
	 * 		Der Wert fuer die Schwerkraft der Physik.<br /> <b>Wichtig:</b> Dies repraesentiert
	 * 		<i>keinen</i> Wert fuer die (Erd-) Beschleunigungszahl "g" aus der Physik. Schon allein
	 * 		deshalb, weil die Zahl umgekehrt wirkt (s. oben).
	 *
	 * @see ea.Raum#aktivMachen()
	 */
	@Override
	public void schwerkraftSetzen (int schwerkraft) {
		this.schwerkraft = schwerkraft;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void impulsHinzunehmen (Vektor impuls) {
		Logger.error("Aktivobjekte unterstützen leider keine Impulsrechnung. Dafür gibt es die Newton-Körper!");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void geschwindigkeitHinzunehmen (Vektor geschwindigkeit) {
		Logger.error("Aktivobjekte unterstützen leider keine Geschwindigkeit. Dafür gibt es die Newton-Körper!");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public float getLuftwiderstandskoeffizient () {
		Logger.error("Aktivobjekte unterstützen leider keinen Luftwiderstand. Dafür gibt es die Newton-Körper!");
		return 0;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean istBeeinflussbar () {
		Logger.error("Aktivobjekte unterstützen leider keinen Beeinflussbarkeit. Dafür gibt es die Newton-Körper!");
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public float getMasse () {
		Logger.error("Aktivobjekte unterstützen leider keine Masse. Dafür gibt es die Newton-Körper!");
		return 0;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Vektor getForce () {
		Logger.error("Aktivobjekte unterstützen leider keine Kraftrechnung. Dafür gibt es die Newton-Körper!");
		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void luftwiderstandskoeffizientSetzen (float luftwiderstandskoeffizient) {
		Logger.error("Aktivobjekte unterstützen leider keinen Luftwiderstand. Dafür gibt es die Newton-Körper!");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void beeinflussbarSetzen (boolean beeinflussbar) {
		Logger.error("Aktivobjekte unterstützen leider keinen Beeinflussbarkeit. Dafür gibt es die Newton-Körper!");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void masseSetzen (float masse) {
		Logger.error("Aktivobjekte unterstützen leider keine Masse. Dafür gibt es die Newton-Körper!");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void kraftSetzen (Vektor kraft) {
		Logger.error("Aktivobjekte unterstützen leider keine Kraftrechnung. Dafür gibt es die Newton-Körper!");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void geschwindigkeitSetzen (Vektor geschwindigkeit) {
		Logger.error("Aktivobjekte unterstützen leider keine Geschwindigkeit. Dafür gibt es die Newton-Körper!");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void einfluesseZuruecksetzen () {
		Logger.error("Aktivobjekte unterstützen leider keine Einflüsse. Dafür gibt es die Newton-Körper!");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void kraftAnwenden (Vektor kraft, float t_kraftuebertrag) {
		Logger.error("Aktivobjekte unterstützen leider keine Kraftrechnung. Dafür gibt es die Newton-Körper!");
	}

	/**
	 * Vollfuehrt portionsweises (in Pixelschritten) Verschieben auf der X-Richtung.
	 *
	 * @param dX
	 * 		Die x-Änderung (Delta-X)
	 *
	 * @return <code>true</code>, wenn die Bewegung in X-Richtung ohne Passiv-Block moeglich war,
	 * sonst <code>false</code>.
	 */
	public boolean xVersch (float dX) {
		float z;
		int max;
		dX += remainderX;
		if (dX > 0) {
			z = 1;
			max = (int) dX;
		} else if (dX < 0) {
			z = -1;
			max = - (int)dX;
		} else {
			remainderX = dX;
			return true;
		}

		Vektor v = new Vektor(z, 0);
		for (int i = 0; i < max; i++) {
			BoundingRechteck test = ziel.dimension().verschobeneInstanz(v);
			if (physik.inPassivem(test)) {
				return false;
			}
			ziel.verschieben(v);
			dX = dX-z;
		}
		remainderX = dX;
		return true;
	}

	/**
	 * Vollfuehrt portionsweises (in Pixelschritten) Verschieben auf der Y-Richtung unter
	 * beruecksichtung von SChwerkrafteigenschaften.
	 *
	 * @param dY
	 * 		Die y-Änderung (Delta-Y)
	 *
	 * @return <code>true</code>, wenn die Bewegung in Y-Richtung ohne Passiv-Block moeglich war,
	 * sonst <code>false</code>.
	 */
	public boolean yVersch (float dY) {
		float z;
		int max;
		dY += remainderX;
		if (dY > 0) {
			z = 1;
			max = (int) dY;
		} else if (dY < 0) {
			z = -1;
			max = - (int)dY;
		} else {
			remainderY = dY;
			return true;
		}
		Vektor v = new Vektor(0, z);
		for (int i = 0; i < max; i++) {
			BoundingRechteck test = ziel.dimension().verschobeneInstanz(v);
			if (physik.inPassivem(test)) {
				yTrend = 0;
				if (z > 0) {
					sListener.stehReagieren();
				}
				zuletztGefallen = false;
				return false;
			}
			zuletztGefallen = true;
			ziel.verschieben(v);
			dY = dY-z;
		}
		remainderY = dY;
		return true;
	}
}